<?php
namespace app\adminapiv3\service\asset_manage\host;

/**
 * @name: 主机接口逻辑层
 * @Param: 
 * @Description: 
 * @Author: foo
 * @Date: 2019-10-24 16:09:41
 */
use app\adminapiv3\model\access_log\HostAccessLogModel;
use app\adminapiv3\model\learn_data\HostLearnDataModel;
use app\adminapiv3\model\ServerAuthModuleModel;
use app\adminapiv3\service\CommonHostService;
use app\adminapiv3\service\ServerDownload;
use app\adminapiv3\service\SSH;
use app\common\error_message\AssetsGroupError;
use app\common\error_message\AuthError;
use app\common\exception\CustomException;
use app\common\service\asset_manage\address\HostAddress;
use app\common\service\asset_manage\host\Host;
use app\common\service\asset_manage\ministry\HostManager;
use app\common\service\asset_manage\ministry\HostMinistry;
use app\common\service\asset_manage\software\HostSoftware;
use app\common\service\HostContact;
use app\common\service\HostHardware;
use app\common\service\HostLink;
use app\common\service\HostModuleRelation;
use app\common\service\HostPort;
use app\common\service\HostTag;
use app\common\service\HostView;
use app\common\service\platform_manage\ServerAuthService;
use think\Db;
use think\Exception;


class HostService extends CommonHostService
{
    public $module_name = 'client';

    private $remotePath = '/tmp/Install.sh';//远程安装路径
    /**
     * @name: 统计
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-20 14:56:05
     */
    public function statistics ()
    {
        $Host = new Host();
        $HostView = new HostView();
        $return = [
            'os' => $Host->osStatistics(),//操作系统比率
            'tag' => $Host->tagStatistics(),//标签比率
            'top' => [
                'port' => $HostView->topPort() //曲线 端口访问量
            ]
        ]; 

        return $return;
    }


    /**
     * @name: 处理运行中模块
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-25 14:58:43
     */
    public function processRuningModule ($host_id)
    {
        $ServerAuth = new ServerAuthService();
        $moduleList = $ServerAuth->moduleList();
        $moduleData = array_column($moduleList, 'module_name', 'module_key');
        $HostModuleRelation = new HostModuleRelation();
        $hostModule = $HostModuleRelation->findByHostId($host_id);

        $return = [];
        if (empty($hostModule))
        {
            return $return;
        }

        foreach ($hostModule as $k=>$v)
        {
            if(isset($moduleData[$k]) && $v == 1)
            {
                $return[] = $moduleData[$k];
            }
        }
        return $return;
    }

    /**
     * @name: 处理主机端口
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-25 15:56:59
     */
    public function processPort ($host_id)
    {
        $HostPort = new HostPort();
        $data = $HostPort->list(['host_id' => $host_id], 10);


        $return = array_column($data['data'], 'port');

        return $return;
    }

    /**
     * @name: 主机详情
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-25 11:20:58
     */
    public function detail ($host_id)
    {
        $return = $this->info($host_id);
        $return['hardware']= $this->hardware($host_id);//主机硬件使用情况
        $return['runing_module'] = $this->processRuningModule($host_id);//主机运行模块
        $return['open_port'] = $this->processPort($host_id);//开放端口

        return $return;
    }

    /**
     * @name: 详情
     * @Param:
     * @Description:
     * @Author: foo
     * @Date: 2019-10-29 13:28:10
     */
    public function info ($host_id)
    {
        $Host = new Host();
        $data = $Host->info($host_id);

        //设置数据
        $this->softwareId_a[] = $data['software_id'];
        $this->addressId_a[] = $data['address_id'];
        $this->ministryId_a[] = $data['ministry_id'];
        $this->managerId_a[] = $data['manager_id'];
        $this->serviceId_a[] = $data['safe_provider_id'];
        $this->serviceId_a[] = $data['develop_provider_id'];
        $this->serviceId_a[] = $data['maintain_provider_id'];

        $extraData = $this->extraData();
        $return = $this->processOne($extraData, $data);

        return $return;
    }

    /**
     * @name 数据处理
     * @param $data
     * @return array
     * @author wx
     * @date 2020/10/27 14:19
     */
    public function processList($data)
    {
        if (empty($data))
        {
            return [];
        }
        $return = [];

        foreach ($data as  $k => $v)
        {
            $hostId_a[] = $v['id'];

            $this->softwareId_a[] = $v['software_id'];
            $this->addressId_a[] = $v['address_id'];
            $this->ministryId_a[] = $v['ministry_id'];
            $this->managerId_a[] = $v['manager_id'];
            $this->serviceId_a[] = $v['safe_provider_id'];
            $this->serviceId_a[] = $v['develop_provider_id'];
            $this->serviceId_a[] = $v['maintain_provider_id'];
            $v['online'] == 0 ? $offline[] = $v['id'] : '' ;

        }

        $extraData = $this->extraData();


        foreach ($data as $k => $v)
        {
            $return[$k] = $this->processOne($extraData, $v);
        }


        return $return;
    }

    /**
     * @name: 额外的附属数据
     * @Param: $service 服务商数据
     * @Description:
     * @Author: foo
     * @Date: 2019-10-25 13:38:12
     */
    public function extraData ()
    {
        $hostManagerServer = new HostManager();
        $return['manager'] = $hostManagerServer->showManager($this->managerId_a, 'name,phone', 'id');

        $hostMinistryServer = new HostMinistry();
        $return['ministry'] = $hostMinistryServer->showMinistry($this->ministryId_a, 'id,pid,name', 'id');

        $hostTagServer = new HostTag();
        $return['tag'] = $hostTagServer->showTag([], 'id,name,icon', 'id');



        $hostSoftwareServer = new HostSoftware();
        $return['software'] = $hostSoftwareServer->showSoftware($this->softwareId_a, 'id,name,status', 'id');



        $hostAddressServer = new HostAddress();
        $return['address'] = $hostAddressServer->showAddress($this->addressId_a, 'id,name,address', 'id');


        $hostServiceServer = new \app\common\service\asset_manage\service_provider\HostService();

        $return['service'] = $hostServiceServer->showService($this->serviceId_a, 'id,name, contact, phone, status','id');

        return $return;
    }

    /**
     * @name: 单个处理
     * @Param:
     * @Description:
     * @Author: foo
     * @Date: 2019-10-25 13:34:18
     */
    public function processOne ($extraData, $data)
    {

        $params = [
            'id',//客户端id
            'name',//客户端名
            'alias',//客户端别名
            'ip',//客户端ip
            'system_name',//系统名
            'os',//系统类别
            'online',//是否在线
            'version',//客户端/模块 版本号
            'hostkey',//资产绑定码
            'group_id', //资产组id
        ];

        foreach ($params as $v)
        {
            isset($data[$v]) ? $return[$v] = $data[$v] : '';
        }


        !isset($extraData['tag']) ? : $return['tag'] = $this->processTag($extraData['tag'], $data['tag']);
        !isset($extraData['software']) ? : $return['software'] = $this->processSoftware($extraData['software'], $data['software_id']);//安全责任人
        !isset($extraData['ministry']) ? : $return['ministry'] = $this->processMinistry($extraData['ministry'], $data['ministry_id']);//安全责任人
        !isset($extraData['address']) ? : $return['address'] = $this->processAddress($extraData['address'], $data['address_id']);//安全责任人
        !isset($extraData['manager']) ? : $return['manager'] = $this->processSafeManager($extraData['manager'], $data['manager_id']);//安全责任人

        !isset($extraData['service']) ? : $return['develop_provider'] = $this->processService($extraData['service'], $data['develop_provider_id']);//开发商
        !isset($extraData['service']) ? : $return['maintain_provider'] = $this->processService($extraData['service'], $data['maintain_provider_id']);//开发商
        !isset($extraData['service']) ? : $return['safe_provider'] = $this->processService($extraData['service'], $data['safe_provider_id']);//开发商



        return $return;
    }


    /**
     * @name: 添加主机
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-29 10:27:42
     */
    public function add ($hostData, $contactData)
    {
        global $logArray;
        $logArray['content'] = '添加资产';

        //授权判断，当前登录用户所属资产组允许授权数量
        $ServerAuth = new ServerAuthService();
        $remianingNum = $ServerAuth->remainingNum('client');
        if($remianingNum < 1){
            throw new CustomException(AuthError::MODULE_REMIANING_NUMBER_IS_ZERO[1], AuthError::MODULE_REMIANING_NUMBER_IS_ZERO[0]);
        }

        //当前登录用户所属资产组id
        $hostData['group_id'] = $this->getLoginHostGroupId();

        $HostContact = new HostContact();
        $Host = new Host();
        $HostHardware = new HostHardware();
        $HostLink = new HostLink();
        $HostModuleRelation = new HostModuleRelation();
        //学习数据
        $learnDataModel = new HostLearnDataModel();
        //访问日志
        $logModel = new HostAccessLogModel();

        //事务操作
        Db::startTrans();
        try{
            $hostData['hostkey'] = $Host->createHostKey();//生成hostkey
            $host_id = $Host->add($hostData);//添加主机
            if (!$host_id) {
                Db::rollback();
                throw new CustomException('添加主机失败',201);
            }
            $contactData['host_id'] = $host_id;
            //添加ssh登陆表
            if (!$HostContact->add($contactData)) {
                Db::rollback();
                throw new CustomException('添加主机失败',201);
            }
            //添加硬件
            if (!$HostHardware->add($host_id)) {
                Db::rollback();
                throw new CustomException('添加主机失败',201);
            }
            //添加连接信息
            $HostLink->add('client',[$host_id]);

            //模块关系表初始化
            if (!$HostModuleRelation->init($host_id)) {
                Db::rollback();
                throw new CustomException('添加主机失败',201);
            }

            //学习数据:创建数据库
            $createDatabaseResult = $learnDataModel->createChildDatabaseByRoot($host_id);
            //创建数据库结果数据 如果需要创建则返回创建结果 否则返回空数组
            $databaseReturn = $createDatabaseResult['isNeedCreate'] ? [
                'create_database_result' => [
                    [
                        //数据库名称
                        'database' => $createDatabaseResult['db']['name'],
                        //成功与否
                        'result' => $createDatabaseResult['result'],
                        //错误信息
                        'errorMsg' => $createDatabaseResult['result'] ?  '子数据库创建成功' : $createDatabaseResult['msg']
                    ],//...其他库
                ],
            ] : ['create_database_result' => []];

            //如果学习数据创建库失败,返回创建结果
            if(!$createDatabaseResult['result']){
                Db::rollback();
                //在这里直接return返回，不抛出异常,即使错误,也需要弹框提示数据库创建失败
                $this->setError(433,'添加主机失败，子数据库创建失败:'.$createDatabaseResult['db']['name']);
                //返回数据库创建结果
                return $databaseReturn;
            }

            //走到这一步 说明学习数据子库创建已经成功或者不需要创建
            //创建学习数据表
            if (!$learnDataModel->createTable($host_id)) {
                //这里的回滚无法控制到上一步的创库操作
                Db::rollback();
                $this->setError(432,'添加主机失败：创建学习数据表失败');
                //返回数据库创建结果
                return $databaseReturn;
            }

            //访问日志:创建数据库
            $createDatabaseResult2 = $logModel->createChildDatabaseByRoot($host_id);

            //创建数据库结果数据 如果需要创建则返回创建结果 否则返回空数组
            $databaseReturn2 = $createDatabaseResult2['isNeedCreate'] ? [
                'create_database_result' => [
                    [
                        //数据库名称
                        'database' => $createDatabaseResult2['db']['name'],
                        //成功与否
                        'result' => $createDatabaseResult2['result'],
                        //错误信息
                        'errorMsg' => $createDatabaseResult2['result'] ?  '子数据库创建成功' : $createDatabaseResult2['msg']
                    ],//...其他库
                ],
            ] : ['create_database_result' => []];


            //合并之前的结果
            $databaseFinalReturn = [
                'create_database_result' => array_merge($databaseReturn['create_database_result'],$databaseReturn2['create_database_result'])
            ];


            //如果访问日志创建库失败,返回创建结果
            if(!$createDatabaseResult2['result']){
                Db::rollback();
                //在这里直接return返回，不抛出异常,即使错误,也需要弹框提示数据库创建失败
                $this->setError(433,'添加主机失败，子数据库创建失败:'.$createDatabaseResult2['db']['name']);
                //返回数据库创建结果
                return $databaseFinalReturn;
            }

            //创建访问日志表
            if (!$logModel->createTable($host_id)) {
                //这里的回滚无法控制到上一步的创库操作
                Db::rollback();
                $this->setError(432,'添加主机失败：创建访问日志表失败');
                //返回数据库创建结果
                return $databaseFinalReturn;
            }


            //日志数据
            $logArray['new_info'] = '资产名称：'.$hostData['name'].'；IP地址：'.$hostData['ip'];

            //保存数据
            Db::commit();

            //如果是ssh安装方式,则运行安装程序
            if ($contactData['type'] == 2) {
                //ssh连接服务器安装客户端
                if (!$this->installClient($contactData, $hostData['hostkey'])) {

                    //返回数据库创建结果
                    return $databaseFinalReturn;
                }
            }
            //返回数据库创建结果
            return $databaseFinalReturn;
        }catch (Exception $e){
            Db::rollback();
            throw new CustomException('添加主机失败：'.$e->getMessage(),201);
        }

    }

    /**
     * @name: 修改
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-29 11:26:50
     */
    public function modify ($hostData)
    {
        global $logArray;
        $logArray['content'] = '添加资产';

        $Host = new Host();
        $info = $Host->findById($hostData['id']);
        $Host->modify(['id' => $hostData['id']], $hostData);//修改

        $logArray['old_info'] = '资产名称：'.$info['name'].'；IP地址：'.$info['ip'];
        $logArray['new_info'] = '资产名称：'.$hostData['name'].'；IP地址：'.$info['ip'];
    }

    /**
     * @name: 删除/批量删除
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-29 13:18:03
     */
    public function uninstall ($isRecovery)
    {
        global $logArray;
        $logArray['content'] = '删除资产';

        $hostList = $this->getHost();

        $host_id = array_column($hostList, 'id');

        //事务操作
        Db::startTrans();
        try{

            $Host = new Host();
            $Host->uninstall($host_id);
            $HostContact = new HostContact();
            $HostContact->uninstall($host_id);
            $HostModuleRelation = new HostModuleRelation();
            $HostModuleRelation->uninstall($host_id);

            foreach ($hostList as $v){
                $hostName[] = $v['name'];
                //构造是否恢复数据
                $uninstallTmp = [
                    'host_id' => $v['id'],
                    'hostkey' => $v['hostkey'],
                    'is_recovery' => intval($isRecovery),
                    'ctime' => time()
                ];
                $uninstallData[] = $uninstallTmp;
            }

            //添加是否恢复数据
            Db::name('host_uninstall')->insertAll($uninstallData);

            Db::commit();

        }catch (Exception $e){
            Db::rollback();
            throw new CustomException('删除失败：'.$e->getMessage());
        }

        $logArray['old_info'] = '一共删除'.count($hostName).'个资产；资产名称为'.implode('，',$hostName);

        return true;
    }

    /**
     * @name: 主机硬件使用
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-29 14:21:34
     */
    public function hardware ($host_id)
    {
        //查询主机是否在线
        $hostLink = new HostLink();
        $online = $hostLink->checkOnlineByHostId($host_id, 'client');

        $data = [];
        if($online) {
            $HostHardware = new HostHardware();
            $data = $HostHardware->findOne($host_id);
        }


        $return = [];
        if(!empty($data['info']))
        {
            $return = json_decode($data['info'],true);
        }

        return $return;
    }

    /**
     * @name: 检测是否安装
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-29 15:18:06
     */
    public function checkClient ($contactData)
    {
        $filePath = $this->toDo('checkInstallPackage');

        if($contactData['type'] == 2)
        {
            $SSH = new SSH($contactData['ip'], $contactData['user'], $contactData['password'], $contactData['port']);
            $SSH->toDo('sendFile', [$filePath, $this->remotePath]);
        }
    }

    /**
     * @name: ssh 安装客户端
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-06-13 16:35:36
     */
    public function installClient ($contactData, $host_key)
    {
        set_time_limit(0);
        if ($contactData['type'] == 1) {
            return false;
        }

        $SSH = new SSH($contactData['ip'], $contactData['user'], $contactData['pwd'], $contactData['port']);
        $serverIp = $_SERVER['SERVER_ADDR'];
        $sftp = ssh2_sftp($SSH->conn);
        if(!file_exists("ssh2.sftp://{$sftp}/" . $this->remotePath))
        {
            $this->setError($this->CODE_FAIL, $serverIp . '未找到安装包');
            return false;
        }
        try{
            $cmd1 = '[ `id -u` -eq 0 ] && { chmod +x ' . $this->remotePath . ' && ' . $this->remotePath . ' -s ' . $serverIp . ' -k '. $host_key.'; } || { which sudo && echo ' . $contactData['pwd'] . ' | sudo -s  chmod +x' . $this->remotePath . ' && sudo -s ' . $this->remotePath .' -s ' . $serverIp . ' -k ' . $host_key . ' ; }';
            $SSH->toDo('exec', [$cmd1]);
            $cmd2 = 'echo ' . "'". $cmd1 ."'" . ' >> /tmp/install_cmd.txt';
            $SSH->toDo('exec', [$cmd2]);
        }catch (Exception $e){
            $this->setError($this->CODE_FAIL, '客户端安装失败：'.$e->getMessage());
            return false;
        }

        return true;
    }


    /**
     * @name: 端口列表
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-01 15:05:58
     */
    public function portList ($filterData)
    {
        $HostPort = new HostPort();

        $return = $HostPort->list($filterData);

        return $return;
    }


    /**
     * @name: 检查安装包
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-01-06 18:06:03
     */
    public function checkInstallPackage ()
    {
        $ServerDownload = new ServerDownload();
        $fileData = $ServerDownload->moduleFilePath;
        if (!file_exists($fileData['4']))
        {
            $this->setError($this->CODE_FAIL, '未找到指定安装包，请联系管理员处理');
            return false;
        }

        $return = $fileData['4'];

        return $return;
    }

    /**
     * @name 客户端下载
     * @param $type
     * @return bool
     * @author wx
     * @date 2020/10/21 11:01
     */
    public function clientDownload($type)
    {
        if (!in_array($type, ['windows', 'linux'])) {
            $this->setError($this->CODE_FAIL, '参数错误');
            return false;
        }

        $clientArray = array(
            'linux' => 'static/InstallPackage/linux/Install.sh',
            'windows' => 'static/InstallPackage/windows/windows.exe',
        );

        $data['url'] = request()->domain().'/'.$clientArray[$type];
        $data['ip'] = $_SERVER['SERVER_ADDR'];
        $data['port'] = $_SERVER['SERVER_PORT'];
        $data['path'] = $clientArray[$type];

        return $data;
    }

    /**
     * @name 展示所有资产组
     * @return array|\PDOStatement|string|\think\Collection
     * @author wx
     * @date 2020/10/29 10:30
     */
    public function showGroup()
    {
        $roles = Db::name('server_host_group')->order('id asc')->field('id, name')->select();

        return $roles;
    }

    /**
     * @name 资产绑定资产组
     * @param $params
     * @author wx
     * @date 2020/10/21 11:02
     */
    public function bindingGroup($params)
    {
        global $logArray;
        $logArray['content'] = '绑定资产组';

        //1. 查询资产组是否存在
        $groupInfo = Db::name('server_host_group')->where('id', $params['group_id'])->find();
        if(!$groupInfo)
        {
            throw new CustomException(AssetsGroupError::NOT_EXIST[1], AssetsGroupError::NOT_EXIST[0]);
        }

        //2. 验证资产组绑定个数

        //多个主机,去重处理
        $inputHostIds = array_unique(explode(',',$params['host_id']));
        //将输入的数据类型转换为整型
        $inputHostIds = array_map('intval',$inputHostIds);

        //查询之前已绑定的主机id数组
        $oldHostIds = Db::name('host')->where('group_id', $params['group_id'])->column('id');
        //二者id求并集 就是保存后的id数组
        $afterHostIds = array_unique(array_merge($oldHostIds,$inputHostIds));

        //查询该用户所属资产组的client模块的授权上限值
        $moduleModel = new ServerAuthModuleModel();
        $limit = $moduleModel->getClientModuleLimit($params['group_id']);
        if(!$limit){
            throw new CustomException(AuthError::ASSETS_GROUP_UNAUTHORIZED_MODULE[1], AuthError::ASSETS_GROUP_UNAUTHORIZED_MODULE[0]);
        }

        if(count($afterHostIds) > $limit){
            $remain = $limit - count($oldHostIds);
            $remain =  $remain > 0 ? $remain : 0;
            throw new CustomException(AuthError::ADDED_HOST_MORE_THAN_ALLOW_AUTHORIZED[1], AuthError::ADDED_HOST_MORE_THAN_ALLOW_AUTHORIZED[0]);
        }

        //3. 绑定
        Db::name('host')->whereIn('id', $inputHostIds)->update(['group_id' => $params['group_id']]);

        return true;
    }

    /**
     * @name 网卡信息
     * @param $hostId
     * @return array|\PDOStatement|string|\think\Collection
     * @author wx
     * @date 2020/10/28 15:22
     */
    public function network($hostId)
    {
        //查询网卡信息
        $info = Db::name('host_contact')
            ->where('host_id', $hostId)
            ->select();

        return $info;
    }
}